package language;

import compiler.Func;
import java.util.Collections;
import static java.util.Collections.EMPTY_MAP;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.Scanner;

public class Constants {

    private static final Map<String, LType> constants;
    private static final Scanner scanner = new Scanner(System.in);

    public static Map<String, LType> constants() {
        return new HashMap(constants);
    }

    static {
        constants = new HashMap();
        constants.put("!", LFunction.of(l -> {
            return l.toBoolean() ? LNumber.ZERO : LNumber.ONE;
        }));
        constants.put("#", LFunction.of(l -> {
            if (l instanceof LMagnitude) {
                return LFunction.of(m -> {
                    if (m instanceof LMagnitude) {
                        return ((LMagnitude) l).power((LMagnitude) m);
                    }
                    if (m instanceof LArray) {
                        return ((LArray) m).subList(0, ((LMagnitude) l).toInt());
                    }
                    throw new FunctionException("# (on LMagnitude)", l.getClass());
                });
            }
            if (l instanceof LArray) {
                return LFunction.of(m -> {
                    return new LNumber(((LArray) l).indexOf(m));
                });
            }
            throw new FunctionException("#", l.getClass());
        }));
        constants.put("$", LFunction.of(l -> LArray.of(l.toString())));
        constants.put("%", LFunction.of(l -> {
            if (l instanceof LMagnitude) {
                return LFunction.of(m -> {
                    if (m instanceof LMagnitude) {
                        return ((LMagnitude) l).modulo((LMagnitude) m);
                    }
                    if (m instanceof LArray) {
                        return ((LArray) m).rotate(((LMagnitude) l).toInt());
                    }
                    throw new FunctionException("% (on LMagnitude)", l.getClass());
                });
            }
            throw new FunctionException("%", l.getClass());
        }));
        constants.put("&", LFunction.of(l -> {
            if (l instanceof LMagnitude) {
                return LFunction.of(m -> {
                    if (m instanceof LMagnitude) {
                        return ((LMagnitude) l).bitwiseAnd((LMagnitude) m);
                    }
                    throw new FunctionException("& (on LMagnitude)", l.getClass());
                });
            }
            if (l instanceof LArray) {
                return LFunction.of(m -> {
                    if (m instanceof LArray) {
                        return ((LArray) l).intersection((LArray) m);
                    }
                    throw new FunctionException("& (on LArray)", l.getClass());
                });
            }
            throw new FunctionException("&", l.getClass());
        }));
        constants.put("(", LFunction.of(l -> {
            if (l instanceof LMagnitude) {
                return ((LMagnitude) l).subtract(LNumber.ONE);
            }
            if (l instanceof LArray) {
                return ((LArray) l).get(0);
            }
            throw new FunctionException("(", l.getClass());
        }));
        constants.put(")", LFunction.of(l -> {
            if (l instanceof LMagnitude) {
                return ((LMagnitude) l).add(LNumber.ONE);
            }
            if (l instanceof LArray) {
                return ((LArray) l).get(((LArray) l).size() - 1);
            }
            throw new FunctionException("(", l.getClass());
        }));
        constants.put("*", LFunction.of(l -> {
            if (l instanceof LMagnitude) {
                return LFunction.of(m -> {
                    if (m instanceof LMagnitude) {
                        return ((LMagnitude) l).multiply((LMagnitude) m);
                    }
                    if (m instanceof LArray) {
                        return (((LArray) m).repeat((LMagnitude) l));
                    }
                    throw new FunctionException("* (on LMagnitude)", m.getClass());
                });
            }
            if (l instanceof LArray) {
                return LFunction.of(m -> ((LArray) l).insertAt(0, m));
            }
            throw new FunctionException("*", l.getClass());
        }));
        constants.put("+", LFunction.of(l -> {
            if (l instanceof LMagnitude) {
                return LFunction.of(m -> {
                    if (m instanceof LMagnitude) {
                        return ((LMagnitude) l).add((LMagnitude) m);
                    }
                    if (m instanceof LArray) {
                        int num = ((LMagnitude) l).toInt();
                        LArray arr = (LArray) m;
                        return (arr.subList(arr.size() - num, arr.size()));
                    }
                    throw new FunctionException("+ (on LMagnitude)", m.getClass());
                });
            }
            if (l instanceof LArray) {
                return LFunction.of(a -> ((LArray) l).append((LArray) a));
            }
            throw new FunctionException("+", l.getClass());
        }));
        constants.put(",", LFunction.of(l -> {
            if (l instanceof LMagnitude) {
                return LFunction.of(m -> {
                    if (m instanceof LMagnitude) {
                        return LArray.range((LMagnitude) l, ((LMagnitude) m).add(LNumber.ONE));
                    }
                    throw new FunctionException(", (on LMagnitude)", m.getClass());
                });
            }
            if (l instanceof LArray) {
                return LFunction.of(m -> ((LArray) l).insertAtEnd(m));
            }
            throw new FunctionException(",", l.getClass());
        }));
        constants.put("-", LFunction.of(l -> {
            if (l instanceof LMagnitude) {
                return LFunction.of(m -> {
                    if (m instanceof LMagnitude) {
                        return ((LMagnitude) l).subtract((LMagnitude) m);
                    }
                    throw new FunctionException("- (on LMagnitude)", m.getClass());
                });
            }
            if (l instanceof LArray) {
                return LFunction.of(t -> ((LArray) l).remove(t));
            }
            throw new FunctionException("-", l.getClass());
        }));
        constants.put("\\", LFunction.of(l -> {
            if (l instanceof LArray) {
                return LArray.of(((LArray) l).toDebugString());
            }
            throw new FunctionException("\\", l.getClass());
        }));
        constants.put("/", LFunction.of(l -> {
            if (l instanceof LMagnitude) {
                return LFunction.of(m -> {
                    if (m instanceof LMagnitude) {
                        return ((LMagnitude) l).divide((LMagnitude) m);
                    }
                    throw new FunctionException("/ (on LMagnitude)", m.getClass());
                });
            }
            if (l instanceof LArray) {
                return LFunction.of(m -> {
                    if (m instanceof LArray) {
                        return ((LArray) l).splitOnArray((LArray) m);
                    }
                    throw new FunctionException("/ (on LArray)", m.getClass());
                });
            }
            throw new FunctionException("/", l.getClass());
        }));
        constants.put(":", LFunction.of(l -> {
            if (l instanceof LMagnitude) {
                return LFunction.of(m -> LArray.of(Collections.nCopies(((LMagnitude) l).toInt(), m)));
            }
            throw new FunctionException(":", l.getClass());
        }));
        constants.put("<", LFunction.of(l -> {
            if (l instanceof LMagnitude) {
                return LFunction.of(m -> {
                    if (m instanceof LMagnitude) {
                        return LNumber.of(((LMagnitude) l).compareTo((LMagnitude) m) < 0);
                    }
                    throw new FunctionException("< (on LMagnitude)", m.getClass());
                });
            }
            if (l instanceof LArray) {
                return LFunction.of(m -> {
                    if (m instanceof LArray) {
                        return LNumber.of(((LArray) l).compareTo((LArray) m) < 0);
                    }
                    throw new FunctionException("< (on LArray)", m.getClass());
                });
            }
            throw new FunctionException("<", l.getClass());
        }));
        constants.put("=", LFunction.of(l -> {
            if (l instanceof LMagnitude) {
                return LFunction.of(m -> {
                    if (m instanceof LMagnitude) {
                        return LNumber.of(((LMagnitude) l).equals((LMagnitude) m));
                    }
                    throw new FunctionException("= (on LMagnitude)", m.getClass());
                });
            }
            if (l instanceof LArray) {
                return LFunction.of(m -> {
                    if (m instanceof LArray) {
                        return LNumber.of(((LArray) l).equals((LArray) m));
                    }
                    throw new FunctionException("= (on LArray)", m.getClass());
                });
            }
            throw new FunctionException("=", l.getClass());
        }));
        constants.put(">", LFunction.of(l -> {
            if (l instanceof LArray) {
                return LFunction.of(m -> {
                    if (m instanceof LArray) {
                        return ((LArray) l).difference((LArray) m);
                    }
                    throw new FunctionException("> (on LArray)", m.getClass());
                });
            }
            throw new FunctionException(">", l.getClass());
        }));
        constants.put("A", LArray.of("ABCDEFGHIJKLMNOPQRSTUVWXYZ"));
        constants.put("C", LFunction.of(l -> {
            if (l instanceof LArray) {
                return ((LArray) l).splitOnArray(LArray.of(new LCharacter(' ')));
            }
            throw new FunctionException("C", l.getClass());
        }));
        constants.put("F", LFunction.of(l -> {
            if (l instanceof LArray) {
                return LFunction.of(m -> {
                    if (m instanceof LArray) {
                        return LFunction.map((LArray) l, (LArray) m);
                    }
                    throw new FunctionException("F (on LArray)", l.getClass());
                });
            }
            throw new FunctionException("F", l.getClass());
        }));
        constants.put("G", new LNumber(-1));
        constants.put("H", new LNumber(-2));
        constants.put("I", LFunction.of(l -> l));
        constants.put("J", LFunction.of(l -> {
            if (l instanceof LArray) {
                return ((LArray) l).joinOnArray(LArray.of(new LCharacter('\n')));
            }
            throw new FunctionException("J", l.getClass());
        }));
        constants.put("K", LFunction.of(l -> LFunction.of(m -> l)));
        constants.put("L", LArray.EMPTY);
        constants.put("N", new LCharacter('\n'));
        constants.put("O", LNumber.ONE);
        constants.put("P", LNumber.PI);
        constants.put("Q", LSupplier.of(() -> LArray.of(scanner.next())));
        constants.put("R", LFunction.of(l -> {
            if (l instanceof LMagnitude) {
                return LArray.range((LMagnitude) l);
            }
            if (l instanceof LArray) {
                return ((LArray) l).shuffle();
            }
            if (l instanceof Func) {
                return LFunction.of(m -> {
                    if (m instanceof LArray) {
                        return LFunction.of(n -> ((LArray) m).reduce((Func) l, n));
                    }
                    throw new FunctionException("R (on Func)", l.getClass());
                });
            }
            throw new FunctionException("R", l.getClass());
        }));
        constants.put("S", new LCharacter(' '));
        constants.put("T", new LNumber(10));
        constants.put("V", LFunction.of(l -> {
            if (l instanceof LArray) {
                return LFunction.of(m -> {
                    if (m instanceof LArray) {
                        return ((LArray) l).removeAll((LArray) m);
                    }
                    throw new FunctionException("V (on LArray)", l.getClass());
                });
            }
            throw new FunctionException("V", l.getClass());
        }));
        constants.put("W", new LNumber(2));
        constants.put("X", new LNumber(16));
        constants.put("Z", LNumber.ZERO);
        constants.put("^", LFunction.of(l -> {
            if (l instanceof LMagnitude) {
                return LFunction.of(m -> {
                    if (m instanceof LMagnitude) {
                        return ((LMagnitude) l).bitwiseXor((LMagnitude) m);
                    }
                    throw new FunctionException("^ (on LMagnitude)", l.getClass());
                });
            }
            if (l instanceof LArray) {
                return LFunction.of(m -> {
                    if (m instanceof LArray) {
                        return ((LArray) l).symmetricDifference((LArray) m);
                    }
                    throw new FunctionException("^ (on LArray)", l.getClass());
                });
            }
            throw new FunctionException("^", l.getClass());
        }));
        constants.put("a", LFunction.of(l -> {
            if (l instanceof LMagnitude) {
                return ((LMagnitude) l).abs();
            }
            if (l instanceof LArray) {
                return LFunction.of(m -> {
                    return LFunction.of(n -> {
                        return ((LArray) l).replace(m, n);
                    });
                });
            }
            if (l instanceof Func) {
                return LFunction.of(m -> {
                    if (m instanceof LArray) {
                        return LFunction.of(n -> {
                            return ((LArray) m).replaceWhere((Func) l, n);
                        });
                    }
                    throw new FunctionException("a (on Func)", m.getClass());
                });
            }
            throw new FunctionException("a", l.getClass());
        }));
        constants.put("b", LFunction.of(l -> {
            if (l instanceof LMagnitude) {
                return LFunction.of(m -> {
                    if (m instanceof LMagnitude) {
                        return LMagnitude.fromBase10((LMagnitude) l, (LMagnitude) m);
                    }
                    if (m instanceof LArray) {
                        return LMagnitude.toBase10((LMagnitude) l, (LArray) m);
                    }
                    throw new FunctionException("b (on LMagnitude)", m.getClass());
                });
            }
            if (l instanceof LArray) {
                return LFunction.of(m -> {
                    if (m instanceof LMagnitude) {
                        return LFunction.of(n -> {
                            if (n instanceof LMagnitude) {
                                return LMagnitude.baseConv((LMagnitude) m, (LMagnitude) n, (LArray) l);
                            }
                            throw new FunctionException("b (on LArray, LMagnitude)", m.getClass());
                        });
                    }
                    if (m instanceof LArray) {
                        return LFunction.of(n -> {
                            return ((LArray) l).replaceAll((LArray) m, n);
                        });
                    }
                    throw new FunctionException("a (on LArray)", m.getClass());
                });
            }
            throw new FunctionException("b", l.getClass());
        }));
        constants.put("c", LFunction.of(l -> {
            if (l instanceof LMagnitude) {
                return ((LMagnitude) l).asCharacter();
            }
            if (l instanceof LArray) {
                return LFunction.of(m -> {
                    return new LNumber(((LArray) l).count(m));
                });
            }
            if (l instanceof Func) {
                return LFunction.of(m -> {
                    if (m instanceof Func) {
                        return Func.compose((Func) l, (Func) m);
                    }
                    throw new FunctionException("c (on Func)", l.getClass());
                });
            }
            throw new FunctionException("c", l.getClass());
        }));
        constants.put("f", LFunction.of(l -> {
            if (l instanceof LArray) {
                return ((LArray) l).flatten();
            }
            if (l instanceof Func) {
                return LFunction.of(m -> {
                    if (m instanceof LArray) {
                        return ((LArray) m).filter((Func) l);
                    }
                    throw new FunctionException("f (on Func)", l.getClass());
                });
            }
            throw new FunctionException("f", l.getClass());
        }));
        constants.put("g", LFunction.of(l -> {
            if (l instanceof LMagnitude) {
                return LFunction.of(m -> {
                    if (m instanceof LArray) {
                        return ((LArray) m).get((int) ((LMagnitude) l).toDouble());
                    }
                    throw new FunctionException("g (on LMagnitude)", m.getClass());
                });
            }
            if (l instanceof LArray) {
                return LFunction.of(m -> {
                    if (m instanceof LArray) {
                        return LFunction.of(n -> {
                            if (n instanceof LArray) {
                                return LArray.of((l.toString().replaceAll(m.toString(), n.toString())));
                            }
                            throw new FunctionException("g (on LArray, LArray)", m.getClass());
                        });
                    }
                    throw new FunctionException("g (on LArray)", m.getClass());
                });
            }
            throw new FunctionException("g", l.getClass());
        }));
        constants.put("i", LFunction.of(l -> {
            if (l instanceof LMagnitude) {
                return ((LMagnitude) l).roundToZero();
            }
            throw new FunctionException("i", l.getClass());
        }));
        constants.put("j", LFunction.of(l -> {
            if (l instanceof LArray) {
                return LFunction.of(m -> {
                    if (m instanceof LArray) {
                        return ((LArray) l).joinOnArray((LArray) m);
                    }
                    throw new FunctionException("j (on LArray)", l.getClass());
                });
            }
            if (l instanceof Func) {
                return LFunction.of(m -> {
                    if (m instanceof LArray) {
                        return LFunction.of(n -> {
                            if (n instanceof LArray) {
                                return ((LArray) m).leftJoin((LArray) n, (Func) l);
                            }
                            throw new FunctionException("j (on Func, LArray)", l.getClass());
                        });
                    }
                    throw new FunctionException("j (on Func)", l.getClass());
                });
            }
            throw new FunctionException("j", l.getClass());
        }));
        constants.put("k", LFunction.of(l -> {
            if (l instanceof Func) {
                return LFunction.of(m -> {
                    if (m instanceof LArray) {
                        return LFunction.of(n -> {
                            if (n instanceof LArray) {
                                return ((LArray) m).crossJoin((LArray) n, (Func) l);
                            }
                            throw new FunctionException("k (on Func, LArray)", l.getClass());
                        });
                    }
                    throw new FunctionException("k (on Func)", l.getClass());
                });
            }
            throw new FunctionException("k", l.getClass());
        }));
        constants.put("l", LFunction.of(l -> {
            if (l instanceof LMagnitude) {
                return LFunction.of(m -> {
                    if (m instanceof LMagnitude) {
                        return ((LMagnitude) l).log((LMagnitude) m);
                    }
                    throw new FunctionException("l (on LMagnitude)", l.getClass());
                });
            }
            if (l instanceof LArray) {
                return new LNumber(((LArray) l).size());
            }
            throw new FunctionException("l", l.getClass());
        }));
        constants.put("m", LFunction.of(l -> {
            if (l instanceof LMagnitude) {
                return LFunction.of(m -> {
                    if (m instanceof LMagnitude) {
                        return ((LMagnitude) l).subtract((LMagnitude) m);
                    }
                    throw new FunctionException("m (on LMagnitude)", l.getClass());
                });
            }
            if (l instanceof Func) {
                return LFunction.of(m -> {
                    if (m instanceof LArray) {
                        return ((LArray) m).map((Func) l, EMPTY_MAP);
                    }
                    throw new FunctionException("m (on Func)", l.getClass());
                });
            }
            throw new FunctionException("m", l.getClass());
        }));
        constants.put("n", LFunction.of(l -> {
            if (l instanceof LMagnitude) {
                return new LNumber((LMagnitude) l);
            }
            if (l instanceof LArray) {
                LArray arr = (LArray) l;
                return (arr.size() == 0 || arr.get(0) instanceof Func)
                       ? LFunction.of(arr::applyAllTo)
                       : LNumber.parse(arr);
            }
            throw new FunctionException("n", l.getClass());
        }));
        constants.put("o", LFunction.of(l -> {
            if (l instanceof LArray) {
                LArray arr = (LArray) l;
                if (arr.size() == 0 || arr.get(0) instanceof Func) {
                    return LFunction.of(m -> {
                        if (m instanceof LArray) {
                            return ((LArray<? extends Func>) l).eachOnEach((LArray) m);
                        }
                        throw new FunctionException("o (on LArray)", m.getClass());
                    });
                } else {
                    return Utils.openFile(arr);
                }
            }
            if (l instanceof Func) {
                return LFunction.of(m -> {
                    if (m instanceof LArray) {
                        return ((LArray) m).map2((Func) l, EMPTY_MAP);
                    }
                    throw new FunctionException("o (on Func)", l.getClass());
                });
            }
            throw new FunctionException("o", l.getClass());
        }));
        constants.put("p", LFunction.of(l -> {
            System.out.print(l);
            return l;
        }));
        constants.put("r", LFunction.of(l -> {
            if (l instanceof LMagnitude) {
                LMagnitude mag = (LMagnitude) l;
                double d = mag.toDouble();
                if (d > 0) {
                    return new LNumber(new Random().nextInt((int) d));
                }
                if (d < 0) {
                    return new LNumber(-new Random().nextInt(-(int) d));
                }
                return LNumber.ZERO;
            }
            if (l instanceof LArray) {
                LArray arr = (LArray) l;
                if (arr.size() == 0) {
                    return LNumber.ZERO;
                }
                int rand = new Random().nextInt(arr.size());
                return arr.get(rand);
            }
            if (l instanceof Func) {
                return LFunction.of(m -> {
                    if (m instanceof LArray) {
                        return ((LArray) m).reduce((Func) l);
                    }
                    throw new FunctionException("r (on Func)", l.getClass());
                });
            }
            throw new FunctionException("r", l.getClass());
        }));
        constants.put("s", LFunction.of(l -> {
            if (l instanceof LMagnitude) {
                return LFunction.of(m -> {
                    if (m instanceof LArray) {
                        return LFunction.of(n -> ((LArray) m).set(((LMagnitude) l).toInt(), n));
                    }
                    throw new FunctionException("s (on LMagnitude)", m.getClass());
                });
            }
            if (l instanceof LArray) {
                return ((LArray) l).sort();
            }
            if (l instanceof Func) {
                return LFunction.of(m -> {
                    if (m instanceof LArray) {
                        return ((LArray) m).sort((Func) l);
                    }
                    throw new FunctionException("s (on Func)", m.getClass());
                });
            }
            throw new FunctionException("s", l.getClass());
        }));
        constants.put("t", LSupplier.of(() -> new LNumber(System.currentTimeMillis())));
        constants.put("u", LFunction.of(l -> {
            if (l instanceof LArray) {
                return ((LArray) l).unique();
            }
            throw new FunctionException("u", l.getClass());
        }));
        constants.put("v", LFunction.of(l -> {
            if (l instanceof LArray) {
                return ((LArray) l).reverse();
            }
            throw new FunctionException("v", l.getClass());
        }));
        constants.put("w", LFunction.of(LArray::of));
        constants.put("{", LArrayBuilder.EMPTY_ARRAY_BUILDER);
    }
}
